﻿using CodeGenerator.CSharp;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Modifier = CodeGenerator.CSharp.Modifiers;

namespace CodeAngel.Domain
{
    /// <summary>
    /// Generates the entity Filter files for an EntityDef:
    ///     * [entity name].Filter.cs - for end developers to add custom behavior, business 
    ///                                 rules, etc. This file will only be created if it does 
    ///                                 not already exist.
    ///     * [entity name].Filter.designer.cs - for auto-generated code
    /// </summary>
    public static class EntityFilterFileGenerator
    {
        #region CreateFilterFilesAsync

        /// <summary>
        /// Creates and writes to disk the entity filter files for the specified EntityDef.
        /// If the designer file already exists, it will be overwritten.  If the develoer
        /// file already exists, it will NOT be overwritten.
        /// </summary>
        public static Task CreateFilterFilesAsync(this EntityDef thisED)
        {
            return Task.Factory.StartNew(() =>
            {
                var Tasks = new Task[] 
                { 
                    DeveloperEntityFilterFileGenerator.WriteToDiskAsync(thisED),
                    DesignerEntityFilterFileGenerator.WriteToDiskAsync(thisED)
                };
                Task.WaitAll(Tasks);
            });
        }

        #endregion

        #region DeveloperEntityCollectionFileGenerator

        /// <summary>
        /// File generator for the developer entity filteer file.  Once created, it will not be regenerated.
        /// This file is for end developers to add custom behavior, business rules, etc.
        /// </summary>
        private class DeveloperEntityFilterFileGenerator
        {
            #region WriteToDiskAsync

            /// <summary>
            /// Creates and writes to disk the developer entity file. If this file already exists, 
            /// it will NOT be overwritten.
            /// </summary>
            public static Task WriteToDiskAsync(EntityDef thisED)
            {
                return Task.Factory.StartNew(() =>
                {
                    var ThisFileGenerator = new DeveloperEntityFilterFileGenerator();
                    ThisFileGenerator.WriteToDisk(thisED);
                });
            }

            #endregion

            #region WriteToDisk

            /// <summary>
            /// Creates and writes to disk the developer entity file. If this file already exists, 
            /// it will NOT be overwritten.
            /// </summary>
            private void WriteToDisk(EntityDef thisED)
            {
                var ThisFB = new FileBuilder();
                ThisFB.ProjectName = thisED.Domain.ProjectName;
                ThisFB.Directory = thisED.EntitiesDirectoryPath;
                ThisFB.FileName = thisED.FileNames.Filter();
                ThisFB.Namespace = thisED.Namespace;

                // Add the using namespaces for this file.
                ThisFB.UsingNamespaces.Add("System");
                ThisFB.UsingNamespaces.Add(thisED.Domain.FrameworkNamespace);

                // Add the class.
                ThisFB.CodeUnits.Add(new DeveloperEntityFilterClassBuilder(thisED));

                // Write this file to disk - THIS FILE SHOULD NEVER BE OVERWRITTEN.
                ThisFB.WriteToDisk(OverwriteExistingFile.No);
            }

            #endregion

            #region DeveloperEntityFilterClassBuilder

            /// <summary>
            /// Builds the develoer entity collection class.
            /// </summary>
            private class DeveloperEntityFilterClassBuilder : ClassBuilder
            {
                #region Constructors

                public DeveloperEntityFilterClassBuilder(EntityDef thisED)
                    : base(thisED.ClassNames.Filter(), CodeGenerator.CSharp.Modifiers.PublicPartial)
                {

                }

                #endregion

            }

            #endregion
        }

        #endregion

        #region DesignerEntityFilterFileGenerator

        /// <summary>
        /// File generator for the auto-generated entity filter file.  This file will be re-created
        /// as the domain model is updated.
        /// </summary>
        private class DesignerEntityFilterFileGenerator
        {
            #region WriteToDiskAsync

            /// <summary>
            /// Creates and writes to disk the Designer entity file. If this file already exists, 
            /// it will be overwritten.
            /// </summary>
            public static Task WriteToDiskAsync(EntityDef thisED)
            {
                return Task.Factory.StartNew(() =>
                {
                    var ThisFileGenerator = new DesignerEntityFilterFileGenerator();
                    ThisFileGenerator.WriteToDisk(thisED);
                });
            }

            #endregion

            #region WriteToDisk

            /// <summary>
            /// Creates and writes to disk the Designer entity filter file. If this file already exists, 
            /// it will NOT be overwritten.
            /// </summary>
            private void WriteToDisk(EntityDef thisED)
            {
                var ThisFB = new FileBuilder();
                ThisFB.ProjectName = thisED.Domain.ProjectName;
                ThisFB.Directory = thisED.EntitiesDirectoryPath;
                ThisFB.FileName = thisED.FileNames.FilterDesigner();
                ThisFB.Namespace = thisED.Namespace;
                ThisFB.IsAutoGenerated = true;

                // Add the using namespaces for this file.
                ThisFB.UsingNamespaces.Add("System");
                ThisFB.UsingNamespaces.Add(thisED.Domain.FrameworkNamespace);

                // Add the class.
                ThisFB.CodeUnits.Add(new DesignerEntityFilterClassBuilder(thisED));

                // Write this file to disk.
                ThisFB.WriteToDisk(OverwriteExistingFile.Yes);
            }

            #endregion

            #region DesignerEntityFilterClassBuilder

            /// <summary>
            /// Builds the auto-generated entity filter class.
            /// </summary>
            private class DesignerEntityFilterClassBuilder : ClassBuilder
            {
                #region Constructors

                public DesignerEntityFilterClassBuilder(EntityDef thisED)
                    : base(thisED.ClassNames.Filter(), Modifier.PublicPartial)
                {
                    this.BaseClass = "Filter";
                    base.Documentation.Summary = "To provide a programable Filter object that can identify a subset of " + thisED.Name + " entities by specifiing zero or more criteria objects.  An instance of this object can be passed into an appropriate Aggregate to retrieve the desired entities that meets this Filter.";

                    this.AddConstrucorProcedures(thisED);
                }

                #endregion

                #region AddConstrucorProcedures

                /// <summary>
                /// Adds the constructors.
                /// </summary>
                public void AddConstrucorProcedures(EntityDef thisED)
                {
                    var ThisRegion = this.AddRegion("Construcors");

                    var DefaultConstructor = ThisRegion.AddConstructor(thisED.ClassNames.Filter(), Modifier.Public);
                    DefaultConstructor.Base = string.Format("\"{0}\", \"{1}\"", thisED.TableName, thisED.PrimaryKeyPropertyDef.DatabaseColumnName);
                    DefaultConstructor.Documentation.Summary = "Creates an empty " + thisED.ClassNames.Filter() + " filter.";
                    DefaultConstructor.Documentation.Remarks = "Add criteria through the Add methods.";
                    this.AddTableJoinClauses(thisED, DefaultConstructor.Body);

                    var ConstructorWithID = ThisRegion.AddConstructor(thisED.ClassNames.Filter(), Modifier.Public);
                    ConstructorWithID.This = "";
                    ConstructorWithID.Documentation.Summary = "Creates this filter with a criertion added for the specified ID.";
                    ConstructorWithID.Documentation.Remarks = "Add additional criteria through the Add methods.";
                    ConstructorWithID.Parameters.Add("id", "Guid", "The id to use for the ID criterion that is created and added to this filter.");
                    ConstructorWithID.Body.Add("this.Add(new " + thisED.CriteriaNamespace + "." + thisED.PrimaryKeyPropertyDef.Name + "(id));");
                }

                /// <summary>
                /// Adds the necessary TableJoin classes.  This enables criteria from related entities to be added to this filter.
                /// </summary>
                private void AddTableJoinClauses(EntityDef thisED, Body body)
                {
                    if (thisED.FilterRelation == null) return;

                    string ParentTable = null;
                    string ParentKey = null;
                    string ChildTable = null;
                    string ChildKey = null;

                    // We will keep track of each table that is joined.  For now, we do not support the same 
                    // table showing up more than once.
                    System.Collections.Specialized.StringCollection AddedTables = new System.Collections.Specialized.StringCollection();
                    AddedTables.Add(thisED.Name);


                    foreach (var ThisFR in thisED.FilterRelation.FilterRelations.GetAll())
                    {
                        //EntityDef ParentED = ThisFR.PropertyDef.ParentEntity;
                        var ParentED = ThisFR.PropertyDef.EntityDef;

                        if (ThisFR.PropertyDef.IsCollectionType)
                        {
                            ParentTable = ParentED.Name;
                            ParentKey = ParentED.PrimaryKeyPropertyDef.Name;
                            ChildTable = ThisFR.PropertyDef.DataTypeEntityDef.Name;
                            ChildKey = ThisFR.PropertyDef.ForeignKeyPropertyDef.DatabaseColumnName;

                        }
                        else
                        {
                            ParentTable = ParentED.Name;
                            ParentKey = ThisFR.PropertyDef.DatabaseColumnName;
                            ChildTable = ThisFR.PropertyDef.DataTypeEntityDef.Name;
                            ChildKey = ThisFR.PropertyDef.DataTypeEntityDef.PrimaryKeyPropertyDef.Name;
                        }

                        if (!AddedTables.Contains(ChildTable))
                        {
                            body.Add("this.TableJoins.Add(new TableJoin(\"{0}\", \"{1}\", \"{2}\", \"{3}\"));", ParentTable, ParentKey, ChildTable, ChildKey);
                            AddedTables.Add(ChildTable);
                        }

                    }

                }

                #endregion

            }

            #endregion

        }

        #endregion
    }
}
